/******************************************************************************* * Copyright (c) 2000, 2017 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.dltk.internal.corext.refactoring.reorg; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.DLTKFeatures; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.IDLTKLanguageToolkit; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IScriptFolder; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.internal.corext.refactoring.RefactoringAvailabilityTester; import org.eclipse.dltk.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.dltk.internal.corext.refactoring.participants.ResourceProcessors; import org.eclipse.dltk.internal.corext.refactoring.participants.ScriptProcessors; import org.eclipse.dltk.internal.corext.refactoring.tagging.ICommentProvider; import org.eclipse.dltk.internal.corext.refactoring.util.ModelElementUtil; import org.eclipse.dltk.internal.corext.refactoring.util.ResourceUtil; import org.eclipse.dltk.internal.corext.refactoring.util.TextChangeManager; import org.eclipse.dltk.internal.corext.util.Messages; import org.eclipse.dltk.internal.corext.util.Resources; import org.eclipse.dltk.ui.DLTKUIPlugin; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.ltk.core.refactoring.participants.DeleteProcessor; import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker; import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; public final class ScriptDeleteProcessor extends DeleteProcessor implements ICommentProvider { private boolean fWasCanceled; private Object[] fElements; private IResource[] fResources; private IModelElement[] fScriptElements; private IReorgQueries fDeleteQueries; private DeleteModifications fDeleteModifications; private String fComment; private Change fDeleteChange; private boolean fDeleteSubPackages; public static final String IDENTIFIER = "org.eclipse.dltk.ui.DeleteProcessor"; //$NON-NLS-1$ public ScriptDeleteProcessor(Object[] elements) { fElements = elements; fResources = RefactoringAvailabilityTester.getResources(elements); fScriptElements = RefactoringAvailabilityTester .getScriptElements(elements); fDeleteSubPackages = false; fWasCanceled = false; } // ---- IRefactoringProcessor // --------------------------------------------------- @Override public String getIdentifier() { return IDENTIFIER; } @Override public boolean isApplicable() throws CoreException { if (fElements.length == 0) return false; if (fElements.length != fResources.length + fScriptElements.length) return false; for (int i = 0; i < fResources.length; i++) { if (!RefactoringAvailabilityTester.isDeleteAvailable(fResources[i])) return false; } for (int i = 0; i < fScriptElements.length; i++) { if (!RefactoringAvailabilityTester .isDeleteAvailable(fScriptElements[i])) return false; } return true; } public boolean needsProgressMonitor() { if (fResources != null && fResources.length > 0) return true; if (fScriptElements != null) { for (int i = 0; i < fScriptElements.length; i++) { int type = fScriptElements[i].getElementType(); if (type <= IModelElement.SOURCE_MODULE) return true; } } return false; } @Override public String getProcessorName() { return RefactoringCoreMessages.DeleteRefactoring_7; } @Override public Object[] getElements() { return fElements; } @Override public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants shared) throws CoreException { return fDeleteModifications.loadParticipants(status, this, getAffectedProjectNatures(), shared); } private String[] getAffectedProjectNatures() throws CoreException { String[] jNatures = ScriptProcessors .computeAffectedNaturs(fScriptElements); String[] rNatures = ResourceProcessors .computeAffectedNatures(fResources); Set<String> result = new HashSet<>(); result.addAll(Arrays.asList(jNatures)); result.addAll(Arrays.asList(rNatures)); return result.toArray(new String[result.size()]); } public void setDeleteSubPackages(boolean selection) { fDeleteSubPackages = selection; } public boolean getDeleteSubPackages() { return fDeleteSubPackages; } public boolean hasSubPackagesToDelete() { try { for (int i = 0; i < fScriptElements.length; i++) { if (fScriptElements[i] instanceof IScriptFolder) { IScriptFolder scriptFolder = (IScriptFolder) fScriptElements[i]; if (scriptFolder.isRootFolder()) continue; // see bug 132576 (can remove this if(..) // continue; statement when bug is fixed) if (scriptFolder.hasSubfolders()) return true; } } } catch (ModelException e) { DLTKUIPlugin.log(e); } return false; } public void setQueries(IReorgQueries queries) { Assert.isNotNull(queries); fDeleteQueries = queries; } public IModelElement[] getScriptElementsToDelete() { return fScriptElements; } public boolean wasCanceled() { return fWasCanceled; } public IResource[] getResourcesToDelete() { return fResources; } /* * (non-Javadoc) * * @see org.eclipse.dltk.internal.corext.refactoring.base.Refactoring# * checkActivation(org.eclipse.core.runtime.IProgressMonitor) */ @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { Assert.isNotNull(fDeleteQueries);// must be set before checking // activation RefactoringStatus result = new RefactoringStatus(); result.merge(RefactoringStatus.create( Resources.checkInSync(ReorgUtils.getNotLinked(fResources)))); IResource[] javaResources = ReorgUtils.getResources(fScriptElements); result.merge(RefactoringStatus.create( Resources.checkInSync(ReorgUtils.getNotLinked(javaResources)))); for (int i = 0; i < fScriptElements.length; i++) { // IModelElement element= fScriptElements[i]; // if (element instanceof IType && ((IType)element).isAnonymous()) { // // work around for bug // https://bugs.eclipse.org/bugs/show_bug.cgi?id=44450 // // result.addFatalError("Currently, there isn't any support to // delete an anonymous type."); // } } return result; } /* * (non-Javadoc) * * @see * org.eclipse.dltk.internal.corext.refactoring.base.Refactoring#checkInput( * org.eclipse.core.runtime.IProgressMonitor) */ @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException { pm.beginTask(RefactoringCoreMessages.DeleteRefactoring_1, 1); try { fWasCanceled = false; RefactoringStatus result = new RefactoringStatus(); recalculateElementsToDelete(); TextChangeManager manager = new TextChangeManager(); fDeleteChange = DeleteChangeCreator.createDeleteChange(manager, fResources, fScriptElements, getProcessorName()); checkDirtySourceModules(result); checkDirtyResources(result); fDeleteModifications = new DeleteModifications(); fDeleteModifications.delete(fResources); fDeleteModifications.delete(fScriptElements); fDeleteModifications.postProcess(); ResourceChangeChecker checker = context .getChecker(ResourceChangeChecker.class); IResourceChangeDescriptionFactory deltaFactory = checker .getDeltaFactory(); fDeleteModifications.buildDelta(deltaFactory); IFile[] files = getBuildpathFiles(); for (int i = 0; i < files.length; i++) { deltaFactory.change(files[i]); } files = ResourceUtil.getFiles(manager.getAllSourceModules()); for (int i = 0; i < files.length; i++) { deltaFactory.change(files[i]); } return result; } catch (OperationCanceledException e) { fWasCanceled = true; throw e; } catch (ModelException e) { throw e; } catch (CoreException e) { throw new ModelException(e); } finally { pm.done(); } } private void checkDirtySourceModules(RefactoringStatus result) throws CoreException { if (fScriptElements == null || fScriptElements.length == 0) return; for (int je = 0; je < fScriptElements.length; je++) { IModelElement element = fScriptElements[je]; if (element instanceof ISourceModule) { checkDirtySourceModule(result, (ISourceModule) element); } else if (element instanceof IScriptFolder) { ISourceModule[] units = ((IScriptFolder) element) .getSourceModules(); for (int u = 0; u < units.length; u++) { checkDirtySourceModule(result, units[u]); } } } } private void checkDirtySourceModule(RefactoringStatus result, ISourceModule cunit) { IResource resource = cunit.getResource(); if (resource == null || resource.getType() != IResource.FILE) return; checkDirtyFile(result, (IFile) resource); } private void checkDirtyResources(final RefactoringStatus result) throws CoreException { for (int i = 0; i < fResources.length; i++) { IResource resource = fResources[i]; resource.accept((IResourceVisitor) visitedResource -> { if (visitedResource instanceof IFile) { checkDirtyFile(result, (IFile) visitedResource); } return true; }, IResource.DEPTH_INFINITE, false); } } private void checkDirtyFile(RefactoringStatus result, IFile file) { if (file == null || !file.exists()) return; ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager() .getTextFileBuffer(file.getFullPath(), LocationKind.NORMALIZE); if (buffer != null && buffer.isDirty()) { if (buffer.isStateValidated() && buffer.isSynchronized()) { result.addWarning(Messages.format( RefactoringCoreMessages.ScriptDeleteProcessor_unsaved_changes, file.getFullPath().toString())); } else { result.addFatalError(Messages.format( RefactoringCoreMessages.ScriptDeleteProcessor_unsaved_changes, file.getFullPath().toString())); } } } /* * The set of elements that will eventually be deleted may be very different * from the set originally selected - there may be fewer, more or different * elements. This method is used to calculate the set of elements that will * be deleted - if necessary, it asks the user. */ private void recalculateElementsToDelete() throws CoreException { // the sequence is critical here if (fDeleteSubPackages) /* * add subpackages first, to allow removing * elements with parents in selection etc. */ addSubPackages(); removeElementsWithParentsInSelection(); /* * ask before adding empty cus - * you don't want to ask if you, * for example delete the * package, in which the cus * live */ removeUnconfirmedFoldersThatContainSourceFolders(); /* * a selected folder * may be a parent * of a source * folder we must * inform the user * about it and ask * if ok to delete * the folder */ removeUnconfirmedReferencedArchives(); addEmptySourceModulesToDelete(); removeScriptElementsChildrenOfScriptElements();/* * because adding cus * may create elements * (types in cus) whose * parents are in * selection */ confirmDeletingReadOnly(); /* * after empty cus - you want to ask for all * cus that are to be deleted */ addDeletableParentPackagesOnPackageDeletion(); /* * do not change the * sequence in * fScriptElements after * this method */ } /** * Adds all subpackages of the selected packages to the list of items to be * deleted. * * @throws ModelException */ private void addSubPackages() throws ModelException { final Set<IModelElement> modelElements = new HashSet<>(); for (int i = 0; i < fScriptElements.length; i++) { if (fScriptElements[i] instanceof IScriptFolder) { modelElements.addAll( Arrays.asList(ModelElementUtil.getPackageAndSubpackages( (IScriptFolder) fScriptElements[i]))); } else { modelElements.add(fScriptElements[i]); } } fScriptElements = modelElements .toArray(new IModelElement[modelElements.size()]); } /** * Add deletable parent packages to the list of items to delete. * * @throws CoreException */ private void addDeletableParentPackagesOnPackageDeletion() throws CoreException { final List/* <IScriptFolder */ initialPackagesToDelete = ReorgUtils .getElementsOfType(fScriptElements, IModelElement.SCRIPT_FOLDER); if (initialPackagesToDelete.size() == 0) return; // Move from inner to outer packages Collections.sort(initialPackagesToDelete, (arg0, arg1) -> { IScriptFolder one = (IScriptFolder) arg0; IScriptFolder two = (IScriptFolder) arg1; return two.getElementName().compareTo(one.getElementName()); }); // Get resources andscriptelements which will be deleted as well final Set/* <IResource> */ deletedChildren = new HashSet(); deletedChildren.addAll(Arrays.asList(fResources)); for (int i = 0; i < fScriptElements.length; i++) { if (!ReorgUtils.isInsideSourceModule(fScriptElements[i])) deletedChildren.add(fScriptElements[i].getResource()); } // new package list in the right sequence final List<IScriptFolder> allFragmentsToDelete = new ArrayList<>(); for (Iterator outerIter = initialPackagesToDelete.iterator(); outerIter .hasNext();) { final IScriptFolder currentScriptFolder = (IScriptFolder) outerIter .next(); // The package will at least be cleared allFragmentsToDelete.add(currentScriptFolder); if (canRemoveCompletely(currentScriptFolder, initialPackagesToDelete)) { final IScriptFolder parent = ModelElementUtil .getParentSubpackage(currentScriptFolder); if (parent != null && !initialPackagesToDelete.contains(parent)) { final List/* <IScriptFolder> */ emptyParents = new ArrayList(); addDeletableParentPackages(parent, initialPackagesToDelete, deletedChildren, emptyParents); // Add parents in the right sequence (inner to outer) allFragmentsToDelete.addAll(emptyParents); } } } // Remove resources in deleted packages; and the packages as well final List<IModelElement> modelElements = new ArrayList<>(); for (int i = 0; i < fScriptElements.length; i++) { if (!(fScriptElements[i] instanceof IScriptFolder)) { // remove children of deleted packages final IScriptFolder frag = (IScriptFolder) fScriptElements[i] .getAncestor(IModelElement.SCRIPT_FOLDER); if (!allFragmentsToDelete.contains(frag)) modelElements.add(fScriptElements[i]); } } // Re-add deleted packages - note the (new) sequence modelElements.addAll(allFragmentsToDelete); // Remove resources in deleted folders final List<IResource> resources = new ArrayList<>(); for (int i = 0; i < fResources.length; i++) { IResource parent = fResources[i]; if (parent.getType() == IResource.FILE) parent = parent.getParent(); if (!deletedChildren.contains(parent)) resources.add(fResources[i]); } fScriptElements = modelElements .toArray(new IModelElement[modelElements.size()]); fResources = resources.toArray(new IResource[resources.size()]); } /** * Returns true if this initially selected package is really deletable (if * it has non-selected subpackages, it may only be cleared). * */ private boolean canRemoveCompletely(IScriptFolder pack, List packagesToDelete) throws ModelException { final IScriptFolder[] subPackages = ModelElementUtil .getPackageAndSubpackages(pack); for (int i = 0; i < subPackages.length; i++) { if (!(subPackages[i].equals(pack)) && !(packagesToDelete.contains(subPackages[i]))) return false; } return true; } /** * Adds deletable parent packages of the fragment "frag" to the list * "deletableParentPackages"; also adds the resources of those packages to * the set "resourcesToDelete". * */ private void addDeletableParentPackages(IScriptFolder frag, List initialPackagesToDelete, Set resourcesToDelete, List deletableParentPackages) throws CoreException { if (frag.getResource().isLinked()) { final IConfirmQuery query = fDeleteQueries.createYesNoQuery( RefactoringCoreMessages.ScriptDeleteProcessor_confirm_linked_folder_delete, false, IReorgQueries.CONFIRM_DELETE_LINKED_PARENT); if (!query.confirm(Messages.format( RefactoringCoreMessages.ScriptDeleteProcessor_delete_linked_folder_question, new String[] { frag.getResource().getName() }))) return; } final IResource[] children = (((IContainer) frag.getResource())) .members(); for (int i = 0; i < children.length; i++) { // Child must be a package fragment already in the list, // or a resource which is deleted as well. if (!resourcesToDelete.contains(children[i])) return; } resourcesToDelete.add(frag.getResource()); deletableParentPackages.add(frag); final IScriptFolder parent = ModelElementUtil.getParentSubpackage(frag); if (parent != null && !initialPackagesToDelete.contains(parent)) addDeletableParentPackages(parent, initialPackagesToDelete, resourcesToDelete, deletableParentPackages); } // ask for confirmation of deletion of all package fragment roots that are // on buildpaths of other projects private void removeUnconfirmedReferencedArchives() throws ModelException { String queryTitle = RefactoringCoreMessages.DeleteRefactoring_2; IConfirmQuery query = fDeleteQueries.createYesYesToAllNoNoToAllQuery( queryTitle, true, IReorgQueries.CONFIRM_DELETE_REFERENCED_ARCHIVES); removeUnconfirmedReferencedProjectFragments(query); removeUnconfirmedReferencedArchiveFiles(query); } private void removeUnconfirmedReferencedArchiveFiles(IConfirmQuery query) throws ModelException, OperationCanceledException { List<IFile> filesToSkip = new ArrayList<>(0); for (int i = 0; i < fResources.length; i++) { IResource resource = fResources[i]; if (!(resource instanceof IFile)) continue; IScriptProject project = DLTKCore.create(resource.getProject()); if (project == null || !project.exists()) continue; IProjectFragment root = project .findProjectFragment(resource.getFullPath()); if (root == null) continue; List referencingProjects = new ArrayList(1); referencingProjects.add(root.getScriptProject()); referencingProjects.addAll(Arrays .asList(ModelElementUtil.getReferencingProjects(root))); if (skipDeletingReferencedRoot(query, root, referencingProjects)) filesToSkip.add((IFile) resource); } removeFromSetToDelete( filesToSkip.toArray(new IFile[filesToSkip.size()])); } private void removeUnconfirmedReferencedProjectFragments( IConfirmQuery query) throws ModelException, OperationCanceledException { List<IModelElement> rootsToSkip = new ArrayList<>(0); for (int i = 0; i < fScriptElements.length; i++) { IModelElement element = fScriptElements[i]; if (!(element instanceof IProjectFragment)) continue; IProjectFragment root = (IProjectFragment) element; List referencingProjects = Arrays .asList(ModelElementUtil.getReferencingProjects(root)); if (skipDeletingReferencedRoot(query, root, referencingProjects)) rootsToSkip.add(root); } removeFromSetToDelete( rootsToSkip.toArray(new IModelElement[rootsToSkip.size()])); } private static boolean skipDeletingReferencedRoot(IConfirmQuery query, IProjectFragment root, List referencingProjects) throws OperationCanceledException { if (referencingProjects.isEmpty() || root == null || !root.exists() || !root.isArchive()) return false; String question = Messages.format( RefactoringCoreMessages.DeleteRefactoring_3, root.getElementName()); return !query.confirm(question, referencingProjects.toArray()); } private void removeUnconfirmedFoldersThatContainSourceFolders() throws CoreException { String queryTitle = RefactoringCoreMessages.DeleteRefactoring_4; IConfirmQuery query = fDeleteQueries.createYesYesToAllNoNoToAllQuery( queryTitle, true, IReorgQueries.CONFIRM_DELETE_FOLDERS_CONTAINING_SOURCE_FOLDERS); List<IResource> foldersToSkip = new ArrayList<>(0); for (int i = 0; i < fResources.length; i++) { IResource resource = fResources[i]; if (resource instanceof IFolder) { IFolder folder = (IFolder) resource; if (containsSourceFolder(folder)) { String question = Messages.format( RefactoringCoreMessages.DeleteRefactoring_5, folder.getName()); if (!query.confirm(question)) foldersToSkip.add(folder); } } } removeFromSetToDelete( foldersToSkip.toArray(new IResource[foldersToSkip.size()])); } private static boolean containsSourceFolder(IFolder folder) throws CoreException { IResource[] subFolders = folder.members(); for (int i = 0; i < subFolders.length; i++) { if (!(subFolders[i] instanceof IFolder)) continue; IModelElement element = DLTKCore.create(folder); if (element instanceof IProjectFragment) return true; if (element instanceof IScriptFolder) continue; if (containsSourceFolder((IFolder) subFolders[i])) return true; } return false; } private void removeElementsWithParentsInSelection() { ParentChecker parentUtil = new ParentChecker(fResources, fScriptElements); parentUtil.removeElementsWithAncestorsOnList(false); fScriptElements = parentUtil.getScriptElements(); fResources = parentUtil.getResources(); } private void removeScriptElementsChildrenOfScriptElements() { ParentChecker parentUtil = new ParentChecker(fResources, fScriptElements); parentUtil.removeElementsWithAncestorsOnList(true); fScriptElements = parentUtil.getScriptElements(); } private IFile[] getBuildpathFiles() { List result = new ArrayList(); for (int i = 0; i < fScriptElements.length; i++) { IModelElement element = fScriptElements[i]; if (element instanceof IProjectFragment) { IProject project = element.getScriptProject().getProject(); IFile buildpathFile = project.getFile(".classpath"); //$NON-NLS-1$ if (buildpathFile.exists()) result.add(buildpathFile); } } return (IFile[]) result.toArray(new IFile[result.size()]); } @Override public Change createChange(IProgressMonitor pm) throws CoreException { pm.beginTask("", 1); //$NON-NLS-1$ pm.done(); return fDeleteChange; } private void addToSetToDelete(IModelElement[] newElements) { fScriptElements = ReorgUtils.union(fScriptElements, newElements); } private void removeFromSetToDelete(IResource[] resourcesToNotDelete) { fResources = ReorgUtils.setMinus(fResources, resourcesToNotDelete); } private void removeFromSetToDelete(IModelElement[] elementsToNotDelete) { fScriptElements = ReorgUtils.setMinus(fScriptElements, elementsToNotDelete); } // private static IField[] getFields(IModelElement[] elements){ // List fields= new ArrayList(3); // for (int i= 0; i < elements.length; i++) { // if (elements[i] instanceof IField) // fields.add(elements[i]); // } // return (IField[]) fields.toArray(new IField[fields.size()]); // } // ----------- read-only confirmation business ------ private void confirmDeletingReadOnly() throws CoreException { if (!ReadOnlyResourceFinder.confirmDeleteOfReadOnlyElements( fScriptElements, fResources, fDeleteQueries)) throw new OperationCanceledException(); // saying 'no' to this one // is like cancelling the // whole operation } // ----------- empty source modules related method private void addEmptySourceModulesToDelete() throws ModelException { Set<ISourceModule> modulesToEmpty = getCusToEmpty(); addToSetToDelete(modulesToEmpty .toArray(new ISourceModule[modulesToEmpty.size()])); } private Set<ISourceModule> getCusToEmpty() throws ModelException { Set<ISourceModule> result = new HashSet<>(); for (int i = 0; i < fScriptElements.length; i++) { IModelElement element = fScriptElements[i]; ISourceModule module = ReorgUtils.getSourceModule(element); if (module != null && !result.contains(module)) { IDLTKLanguageToolkit toolkit = DLTKLanguageManager .getLanguageToolkit(module); if (toolkit != null && toolkit .get(DLTKFeatures.DELETE_MODULE_WITHOUT_TOP_LEVEL_TYPES) && willHaveAllTopLevelTypesDeleted(module)) { result.add(module); } } } return result; } private boolean willHaveAllTopLevelTypesDeleted(ISourceModule cu) throws ModelException { Set elementSet = new HashSet(Arrays.asList(fScriptElements)); IType[] topLevelTypes = cu.getTypes(); for (int i = 0; i < topLevelTypes.length; i++) { if (!elementSet.contains(topLevelTypes[i])) return false; } return true; } @Override public boolean canEnableComment() { return true; } @Override public String getComment() { return fComment; } @Override public void setComment(String comment) { fComment = comment; } }